#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>
#include "tensor.hpp"
#include <iostream>
#include <iomanip>
#include <list>
#include <cmath>
#include <complex>
#include <unsupported/Eigen/CXX11/Tensor>
#include <log/log.h>


typedef Eigen::Tensor<double,3> Tensor3;
typedef Eigen::Tensor<double,4> Tensor4;
typedef Eigen::Tensor< std::complex<double> ,4> CTensor4;
typedef Eigen::Tensor< std::complex<double> ,3> CTensor3;



namespace sol {
        //template <>
        //struct is_automagical<EigenMatrix> : std::false_type {};
        template <>
        struct is_automagical<Tensor4> : std::false_type {};
        template <>
        struct is_automagical<Tensor3> : std::false_type {};
        template <>
        struct is_automagical<CTensor4> : std::false_type {};
        template <>
        struct is_automagical<CTensor3> : std::false_type {};
}


std::ostream& to_string1(std::ostream& os, const Tensor4& right) {

	os << "Tensor4" <<std::endl;
	if (right.size() == 0) {
		os << "{ Tensor4! empty }";
		return os;
	}
	os << "{ Tensor4 " << right.size() << " | ";
	return os;
}

namespace Tensor{
	std::string serialize( const Eigen::Tensor<double,3>& t3 )
	{
		std::ostringstream sstr;
		sstr << "nl.new_eigen_tensor3(";
		for(int ii=0; ii<3; ii++){
			sstr << t3.dimensions()[ii] << ",";
		}
		sstr << "nl.new_vector({";
		for( int ii=0; ii < t3.size(); ii++ ){
			sstr <<  std::setprecision(15) << t3.data()[ii] << ";";
		}
		sstr <<  "})); ";
		return sstr.str();
	}

	std::string serialize( const Eigen::Tensor<double,4>& t4 )
	{
		std::ostringstream sstr;
		sstr << "nl.new_eigen_tensor4(";
		for(int ii=0; ii<4; ii++){
			sstr << t4.dimensions()[ii] << ",";
		}
		sstr << "nl.new_vector({";
		for( int ii=0; ii < t4.size(); ii++ ){
			sstr <<  std::setprecision(15) << t4.data()[ii] << ";";
		}
		sstr << "})); ";
		return sstr.str();
	}

	std::string serialize( const Eigen::Tensor<std::complex<double>,3>& t3 )
	{
		std::ostringstream sstr;
		sstr << "nl.new_eigen_ctensor3(";
		for(int ii=0; ii<3; ii++){
			sstr << t3.dimensions()[ii] << ",";
		}
		sstr << "nl.new_cvector({";
		for( int ii=0; ii < t3.size(); ii++ ){
			sstr << "{";
			sstr <<  std::setprecision(15) << t3.data()[ii].real() << ";";
			sstr <<  std::setprecision(15) << t3.data()[ii].imag() << "};";
		}
		sstr <<  "})); ";
		return sstr.str();
	}

	std::string serialize( const Eigen::Tensor<std::complex<double>,4>& t4 )
	{
		std::ostringstream sstr;
		sstr << "nl.new_eigen_ctensor4(";
		for(int ii=0; ii<4; ii++){
			sstr << t4.dimensions()[ii] << ",";
		}
		sstr << "nl.new_cvector({";
		for( int ii=0; ii < t4.size(); ii++ ){
			sstr << "{";
			sstr <<  std::setprecision(15) << t4.data()[ii].real() << ";";
			sstr <<  std::setprecision(15) << t4.data()[ii].imag() << "};";
		}
		sstr <<  "})); ";
		return sstr.str();
	}

}

namespace LuaNumeric{ // namespace my_object
	typedef std::complex<double> complex;
	std::vector<int> get_ivector( sol::object obj ){
		std::vector<int> cpp_vector;
		if (obj.is<sol::table>()) {
			sol::table lua_table = obj;
			//std::vector<int> cpp_vector = lua_table;
			cpp_vector = lua_table.as<std::vector<int>>();
		}else if(obj.is<std::vector<int>>() ) {
			cpp_vector = obj.as<std::vector<int>>();
		}else if(obj.is<int>() ) {
			cpp_vector = {obj.as<int>()};
		}
		if(!cpp_vector.size())
			throw "index type error";
		for(auto& v:cpp_vector)
			v -= 1;
		return std::move(cpp_vector);
	};

	void open_luaTensor(sol::table& module) {
		module.set_function("new_eigen_tensor3", sol::overload(
				[](int m1,int m2, int m3) {
					return Tensor3(m1,m2,m3);
				},
				[](int m1,int m2, int m3,const std::vector<double>& vec) {
					if( m1*m2*m3 != vec.size()){
						throw "size do not match at tensor3 init...";
					}
					auto t3 = Tensor3(m1,m2,m3);
					for(int ii=0; ii < vec.size(); ii++){
						t3.data()[ii] = vec[ii];
					}
					return std::move(t3);
				}
			)
		);
		module.set_function("new_eigen_tensor4", sol::overload(
				[](int m1,int m2, int m3,int m4) {
					return Tensor4(m1,m2,m3,m4);
				},
				[](int m1,int m2, int m3,int m4,const std::vector<double>& vec) {
					if( m1*m2*m3*m4 != vec.size()){
						throw "size do not match at tensor4 init...";
					}
					auto t4 = Tensor4(m1,m2,m3,m4);
					for(int ii=0; ii < vec.size(); ii++){
						t4.data()[ii] = vec[ii];
					}
					return std::move(t4);
				}
			)
		);

		module.set_function("new_eigen_ctensor3", sol::overload(
				[](int m1,int m2, int m3) {
					return CTensor3(m1,m2,m3);
				},
				[](int m1,int m2, int m3,const std::vector<std::complex<double> >& vec) {
					if( m1*m2*m3 != vec.size()){
						throw "size do not match at ctensor3 init...";
					}
					auto t3 = CTensor3(m1,m2,m3);
					for(int ii=0; ii < vec.size(); ii++){
						t3.data()[ii] = vec[ii];
					}
					return std::move(t3);
				}
			)
		);
		module.set_function("new_eigen_ctensor4", sol::overload(
				[](int m1,int m2, int m3,int m4) {
					return CTensor4(m1,m2,m3,m4);
				},
				[](int m1,int m2, int m3,int m4,const std::vector<std::complex<double> >& vec) {
					if( m1*m2*m3*m4 != vec.size()){
						throw "size do not match  at ctensor4 init...";
					}
					auto t4 = CTensor4(m1,m2,m3,m4);
					for(int ii=0; ii < vec.size(); ii++){
						t4.data()[ii] = vec[ii];
					}
					return std::move(t4);
				}
			)
		);


		module.new_usertype<Tensor4>("EigenTensor4"
				,sol::no_constructor
				,"resize",[](Tensor4& t,int m1,int m2,int m3,int m4){
					t.resize(m1,m2,m3,m4);
				}
				,"set_zero",[](Tensor4& t){
					t.setZero();
				}
				,"set_constant",[](Tensor4& t,double val){
					t.setConstant(val);
				}
				,"chip",[](Tensor4& t,sol::object luaii,sol::object luajj,
						sol::object luakk,sol::object luall){
					return Tensor::chip<double,std::vector<double>>(t,
							get_ivector(luaii),get_ivector(luajj),
							get_ivector(luakk),get_ivector(luall));
				}
				,sol::meta_function::index, &getter<double>
				,sol::meta_function::new_index, &setter<double,double>
		);

		module.new_usertype<Tensor3>("EigenTensor3"
				,sol::no_constructor
				,"resize",[](Tensor3& t,int m1,int m2,int m3){
					t.resize(m1,m2,m3);
				}
				,"set_zero",[](Tensor3& t){
					t.setZero();
				}
				,"set_constant",[](Tensor3& t,double val){
					t.setConstant(val);
				}
				,sol::meta_function::index, &getter3<double>
				,sol::meta_function::new_index, &setter3<double,double>
		);

		module.new_usertype<CTensor4>("CEigenTensor4"
				,sol::no_constructor
				,"resize",[](CTensor4& t,int m1,int m2,int m3,int m4){
					t.resize(m1,m2,m3,m4);
				}
				,"set_zero",[](CTensor4& t){
					t.setZero();
				}
				,"chip",[](CTensor4& t,sol::object luaii,sol::object luajj,
						sol::object luakk,sol::object luall){
					return Tensor::chip<std::complex<double>,std::vector<std::complex<double>>>(t,
							get_ivector(luaii),get_ivector(luajj),
							get_ivector(luakk),get_ivector(luall));
				}
				,sol::meta_function::index, &getter< std::complex<double> >
				,sol::meta_function::new_index, sol::overload(
							&setter<std::complex<double>,std::complex<double>>
							,&setter<std::complex<double>,double>
							)
		);

		module.new_usertype<CTensor3>("CEigenTensor3"
				,sol::no_constructor
				,"resize",[](CTensor3& t,int m1,int m2,int m3){
					t.resize(m1,m2,m3);
				}
				,"set_zero",[](CTensor3& t){
					t.setZero();
				}
				,sol::meta_function::index, &getter3<std::complex<double>>
				,sol::meta_function::new_index, sol::overload(
						&setter3<std::complex<double>,std::complex<double>>
						,&setter3<std::complex<double>,double>
						)
		);

	}

} // namespace my_object




